package org.nViron.nvOrganic;

//~--- non-JDK imports --------------------------------------------------------

import org.nViron.nvData.nvDataEntity;
import org.nViron.nvOrganism.nvStateMgr;
import org.nViron.nvParams.nvContextParamMgr.CONTEXTPARAM;
import org.nViron.nvParams.nvVizCalculator;
import org.nViron.nvParams.nvVizParamMgr.VIZPARAM;
import org.nViron.nvParams.nvVizValueCache;
import org.nViron.nvRender.nvRenderer;
import org.nViron.nvRender.nvVec3;

import traer.physics.Vector3D;

/**
 * Represents a single information entity that sprouted out of the organic
 * @author Roland Haring <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvLeaf extends nvKinetic implements nvVizCalculator {
    private final nvVizValueCache vizCache = new nvVizValueCache(this);
    private nvDataEntity entity;
    private long nextVelocityChangeStamp;

    public nvLeaf(nvOrganic organic, nvStalk parent, nvDataEntity _entity) {
        super(organic, parent);
        addEntity(_entity);
        vizCache.updateAllVizValues(getOrganismMgr().getSimulator().getVizParamMgr());
        particle = getOrganic().generateParticle(this);

        float c1Val = 50.0f + ((5 - level > 1)
                               ? (5.0f - level) * 15.0f * (float) Math.random()
                               : 0.0f);    // TODO make this dependent on a VIZVALUE

        c1Val += c1Val / 2.0f * Math.random() - c1Val / 4.0f;    // some random +/- 25%
        c1    = getOrganismMgr().getSimulator().getProjection().geoToVizApprox(c1Val);
        c2    = getOrganismMgr().getSimulator().getProjection().geoToVizApprox(40.0f);    // TODO make this dependent on a VIZVALUE
    }

    @Override
    public final void addEntity(nvDataEntity entity) {
        if (this.entity != null) {
            throw new IllegalAccessError("only one entity at a time is allowed!");
        }
        this.entity = entity;
    }

    public float calculateVizValue(VIZPARAM param) {
        if (entity == null) {
            return 0.0f;
        } else {
            return entity.getVizValue(param);
        }
    }

    @Override
    protected boolean doneDegenerating() {
        return ((particle == null)
                || (particle.position().distanceTo(connectingJoint.getSource().getParticle().position())
                    <= getC1() / 10.0f));
    }

    @Override
    protected boolean doneSprouting() {
        boolean bRet = !particle.isAttractionInfluenced() &&!particle.isSpringInfluenced()
                       && (particle.position().distanceTo(connectingJoint.getSource().getParticle().position())
                           >= getC1());

        if (bRet) {

            // System.out.println("SPROUT STOP at " + particle.position().toString());
        }

        return bRet;
    }

    public void dropEntity() {
        entity = null;
    }

    @Override
    public nvVec3 getCurrentRenderPos() {
        nvVec3 pos = connectingJoint.getSource().getCurrentRenderPos();

        // TODO this is a bit of a hack now! (our position is not relative to parent but relative to root)
        pos.x += particle.position().x() - connectingJoint.getSource().getParticle().position().x();
        pos.y += particle.position().y() - connectingJoint.getSource().getParticle().position().y();
        pos.z += particle.position().z() - connectingJoint.getSource().getParticle().position().z();

        return pos;
    }

    public nvDataEntity getEntity() {
        return entity;
    }

    @Override
    public Vector3D getInitialVelocity() {
        Vector3D pos = new Vector3D();

        pos.setX(((float) Math.random() - 0.5f) / 15.0f);    // TODO make this dependent on some VIZPARAM
        pos.setZ(((float) Math.random() - 0.5f) / 15.0f);    // TODO make this dependent on some VIZPARAM
        pos.setY(((float) Math.random() * 0.5f + 0.5f)
                 * getOrganismMgr().getSimulator().getProjection().geoToVizApprox(120.0f));

        return pos;
    }

    @Override
    protected int getNumChildren() {
        return 0;
    }

    @Override
    public int getNumEntities() {
        return ((entity == null)
                ? 0
                : 1);
    }

    public float getSimilarityTo(nvDataEntity entity) {
        return ((this.entity == null)
                ? 0.0f
                : this.entity.getSimilarityTo(entity));
    }

    public float getSimilarityTo(nvDataEntity other, CONTEXTPARAM contextParam) {
        return ((this.entity == null)
                ? 0.0f
                : this.entity.getSimilarityTo(other, contextParam));
    }

    public float getSimilarityTo(nvDataEntity other, VIZPARAM vizParam) {
        return ((this.entity == null)
                ? 0.0f
                : this.entity.getSimilarityTo(other, vizParam));
    }

    @Override
    public float getVizValue(VIZPARAM param) {
        return vizCache.getVizValue(param);
    }

    public boolean hasEntity() {
        return entity != null;
    }

    @Override
    protected void onStateChange(int state) {
        switch (state) {
        case nvStateMgr.SPROUTING :
            particle = getOrganic().generateParticle(this);
            particle.position().set(connectingJoint.getSource().getParticle().position());

            break;

        case nvStateMgr.LIVING :
            particle.velocity().clear();
            nextVelocityChangeStamp = 0;

            break;

        case nvStateMgr.DEGENERATING :
            if (particle != null) {
                particle.velocity().clear();
                getOrganic().getParticleSystem().removeSprings(particle);
                getOrganic().getParticleSystem().removeAttractions(particle);
                particle.makeFree();
                getOrganic().getParticleSystem().makeSpring(particle, connectingJoint.getSource().getParticle(), 10.0f,
                        10.0f, 0.0f);
            }

            break;
        }
    }

    @Override
    public void remove() {
        super.remove();
    }

    @Override
    public boolean removeEntity(nvDataEntity entity) {
        if (this.entity == null) {
            degenerate();
        } else if (this.entity.getId() == entity.getId()) {
            this.entity = null;
            degenerate();

            return true;
        }
        return false;
    }

    public void render(nvRenderer renderer) {
        if (!isRenderable()) {
            return;
        }

        renderer.renderLeaf(this);
    }

    @Override
    public void update(float timeStep) {
        super.update(timeStep);

        if (getState() == nvStateMgr.LIVING) {
            if (System.currentTimeMillis() > nextVelocityChangeStamp) {
                nextVelocityChangeStamp = System.currentTimeMillis() + 1000 + (int) (Math.random() * 3000.0f);    // TODO make this dependent from some VIZVALUE

                float randMove = 0.025f;    // TODO make this dependent from some VIZVALUE

                particle.velocity().set((float) (Math.random() - 0.5f) * randMove, 0.0f,
                                        (float) (Math.random() - 0.5f) * randMove);
            }
        }
    }

    @Override
    public void updateVizValue(VIZPARAM param) {
        vizCache.updateVizValue(param);
    }
}



